﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace collabr8
{
    class Client
    {
        // Will hold the user name
        private string UserName;
        private string UserPassword;
        private TcpClient tcpServer, tcpClip;
        // Needed to update the form with messages from another thread
        public delegate void UpdateLogCallback(string strMessage);
        public delegate void ShowViewer();
        public delegate void CreateClipboard();
        // Needed to set the form to a "disconnected" state from another thread
        public delegate void CloseConnectionCallback(string strReason);
        private Thread thrMessaging, thrClip;
        private IPAddress ipAddr;
        private Int32 port;
        private bool Connected;
        private BinaryFormatter formatter, clipFormatter;
        private Clip inClip, outClip;
        private ClientForm father;
        private Message incoming, outgoing;
        private int width, height;
        private string clipFolder;

        public Client()
        {
            this.Connected = false;
        }
        public Client(ClientForm father)
        {
            this.father = father;
            this.Connected = false;
        }

        public Client(ClientForm father, String ipAddr, int port, String user, String pwd)
        {
            this.father = father;
            this.UserName = user;
            this.UserPassword = pwd;
            this.ipAddr = IPAddress.Parse(ipAddr);
            this.port = port;
            this.Connect(ipAddr, port, user, pwd);
        }

        public bool Connect(String ipAddr, int port, String user, String pwd)
        {
            // Start a new TCP connections to the chat server
            this.ipAddr = IPAddress.Parse(ipAddr);
            this.port = port;
            this.tcpServer = new TcpClient();
            try
            {
                this.tcpServer.Connect(ipAddr, port);
            }
            catch
            {
                return false;
            }

            // Helps us track whether we're connected or not
            this.Connected = true;
            this.clipFolder = user + "_clips";
            // Create the formatter, used to serialize messages:
            this.formatter = new BinaryFormatter();
            Message login = new Message(MessageCode.Connect, user, pwd);
            this.formatter.Serialize(tcpServer.GetStream(), login);

            // Start the thread for receiving messages and further communication
            this.thrMessaging = new Thread(new ThreadStart(ReceiveMessages));
            this.thrMessaging.IsBackground = true;
            this.thrMessaging.Start();

            return true;
        }

        public void ReceiveMessages()
        {
            // Receive the response from the server
            Message response = (Message)this.formatter.Deserialize(tcpServer.GetStream());

            
            if (response.Code == MessageCode.Connect && response.Text == "OK")
            {
                // Update the form to tell it we are now connected
                father.Invoke(new UpdateLogCallback(father.UpdateLog), new object[] { "Connected Successfully!" });
                father.Invoke(new CreateClipboard(father.CreateClipboard));
            }
            else // If the first character is not a 1, the connection was unsuccessful
            {
                string Reason = "Not Connected: ";
                // Extract the reason out of the response message. The reason starts at the 3rd character
                Reason += response.Text;
                // Update the form with the reason why we couldn't connect
                father.Invoke(new CloseConnectionCallback(father.EndChat), new object[] { Reason });
                // Exit the method
                return;
            }
            // While we are successfully connected, read incoming lines from the server


            this.thrClip = new Thread(new ThreadStart(ClipboardConnection));
            this.thrClip.IsBackground = true;
            this.thrClip.Start();

            try
            {
                while (Connected)
                {
                    // Show the messages in the log TextBox
                    this.incoming = (Message)this.formatter.Deserialize(tcpServer.GetStream());

                    switch (this.incoming.Code)
                    {
                        case MessageCode.Chat:
                            
                            father.Invoke(new UpdateLogCallback(father.UpdateLog), new object[] { this.incoming.Text });
                            break;

                        case MessageCode.VideoOn:

                                String[] dims = incoming.Text.Split(';');
                                this.width = Int32.Parse(dims[0]);
                                this.height = Int32.Parse(dims[1]);
                                father.Invoke(new ShowViewer(father.ShowViewer));
                            break;
                    }
    
                }
            }
            catch
            {
                this.CloseConnection();
            }
        }

        public void ClipboardConnection()
        {
            this.tcpClip = new TcpClient();
            try
            {
                this.tcpClip.Connect(ipAddr, port+2);
            }
            catch
            {
                this.CloseConnection();
                father.Invoke(new CloseConnectionCallback(father.EndChat), new object[] { "Server is not responding. You've been disconnected." });
                return;
            }

            // Create the formatter, used to serialize messages:
            this.clipFormatter = new BinaryFormatter();

            // Then start the loop:
            try
            {
                while (Connected)
                {
                    if(this.inClip != null)
                        this.inClip.Dispose();
                    this.inClip = (Clip)this.formatter.Deserialize(tcpClip.GetStream());
                    this.inClip.SaveFile(clipFolder);
                    father.Invoke(father.delegateUpdateClip, new object[] { this.inClip.Name });
                }
            }
            catch
            {
                this.CloseConnection();
            }
        }


        // Closes a current connection
        public void CloseConnection()
        {
            // Close the objects
            this.Connected = false;
            if(this.tcpServer != null)
                this.tcpServer.Close();
            if(this.tcpClip != null)
                this.tcpClip.Close();
        }
        // Sends the message typed in to the server
        public void SendMessage(String message)
        {
            if (message.Length > 0)
            {
                this.outgoing = new Message(message);
                this.formatter.Serialize(tcpServer.GetStream(), this.outgoing);
            }
        }

        public void SendClip(string fname)
        {
           // Try sending the Frame to each
                try
                {
                    if(this.outClip != null)
                        this.outClip.Dispose();
                    this.outClip = new Clip(this.clipFolder + "\\" + fname, fname);
                    this.clipFormatter.Serialize(tcpClip.GetStream(), this.outClip);
                }
                catch // If there was a problem, the user is not there anymore, remove him
                {
                    this.CloseConnection();
                    father.Invoke(new CloseConnectionCallback(father.EndChat), new object[] { "Server is not responding. You've been disconnected." });
                }
        }


        public bool isConnected 
        {
            get 
            {
                return this.Connected;
            }
        }

        public ClientForm Father
        {
            get
            {
                return this.father;
            }

            set
            {
                this.father = value;
            }
        }

        public int Width
        {
            get
            {
                return this.width;
            }
        }

        public int Height
        {
            get
            {
                return this.height;
            }
        }

        public Int32 Port
        {
            get
            {
                return this.port;
            }
        }

        public IPAddress Ip
        {
            get
            {
                return this.ipAddr;
            }
        }

    }
}
